﻿using System;
using System.Threading;
using Lucene.Linq.Utility;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Index;
using Lucene.Net.Search;
using LDirectory = Lucene.Net.Store.Directory;

namespace Lucene.Linq
{
    /// <summary>
    /// Index Context contains the Lucene Searcher and Modifier with locking semantics
    /// </summary>
    public class IndexContext : IDisposable
    {
        #region IDisposable Members

        ///<summary>
        /// Closes the context - searchers, modifiers and leaves the directory in tact
        ///</summary>
        public void Dispose()
        {
            using (EnterWriteLockWithoutSearcherRefresh())
            {
                CloseSearchers();
                CloseModifier();
            }
        }

        #endregion

        #region Nested type: Writer

        private class Writer : IndexWriter
        {
            public Writer(LDirectory directory, Analyzer analyzer)
                : base(directory, analyzer, false)
            {
            }

            #region IDisposable Members

            public override void Dispose()
            {
                Close();
            }

            #endregion
        }

        #endregion

        #region Fields/Properties

        private readonly Analyzer _analyzer;
        private readonly LDirectory _directory;

        private readonly ReaderWriterLockSlim _modifyingLock =
            new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

        private IndexModifier _modifier;
        private IndexSearcher _searcher;

        ///<summary>
        /// The backing Lucene Store which this context is storing
        ///</summary>
        public LDirectory Directory
        {
            get { return _directory; }
        }

        /// <summary>
        /// The index searcher
        /// </summary>
        public IndexSearcher Searcher
        {
            get
            {
                using (EnterReadLock())
                {
                    return _searcher;
                }
            }
        }

        /// <summary>
        /// The index modifier
        /// </summary>
        public IndexModifier Modifier
        {
            get
            {
                using (EnterUpgradableReadLock())
                {
                    return _modifier;
                }
            }
        }

        /// <summary>
        /// The default analyzer
        /// </summary>
        public Analyzer Analyzer
        {
            get { return _analyzer; }
        }

        #endregion

        #region Ctors

        /// <summary>
        /// Creates a context from a lucene backing store and default analyzer
        /// </summary>
        /// <param name="directory">The directory in which to store the index</param>
        /// <param name="analyzer">The default analyzer to use in the context</param>
        public IndexContext(LDirectory directory, Analyzer analyzer)
        {
            if (directory == null)
                throw new ArgumentNullException("directory");

            if (analyzer == null)
                throw new ArgumentNullException("analyzer");

            _directory = directory;

            // Null analyzer is okay
            _analyzer = analyzer;

            // build searcher refs
            ConstructModifier();
            ConstructSearchers();
        }

        #endregion

        #region Modifying Lock Semantics

        /// <summary>
        /// Acquire a write lock (if not already taken) that doesn't refresh searches on disposal
        /// </summary>
        /// <returns>Disposable lock</returns>
        public IDisposable EnterWriteLockWithoutSearcherRefresh()
        {
            if (_modifyingLock.IsWriteLockHeld)
            {
                return new ActionDisposable(() => { });
            }
            IDisposable wl = _modifyingLock.WriteLock();
            return new ActionDisposable(wl.Dispose);
        }

        /// <summary>
        /// Acquires a read lock (if not already taken)
        /// </summary>
        /// <returns>Disposable lock</returns>
        public IDisposable EnterReadLock()
        {
            if (_modifyingLock.IsReadLockHeld)
            {
                return new ActionDisposable(() => { });
            }
            IDisposable wl = _modifyingLock.ReadLock();
            return new ActionDisposable(wl.Dispose);
        }

        /// <summary>
        /// Acquires a read lock (if not already taken) that can be upgraded to a write lock
        /// </summary>
        /// <returns>Disposable lock</returns>
        public IDisposable EnterUpgradableReadLock()
        {
            if (_modifyingLock.IsUpgradeableReadLockHeld)
            {
                return new ActionDisposable(() => { });
            }
            IDisposable wl = _modifyingLock.UpgradableReadLock();
            return new ActionDisposable(wl.Dispose);
        }

        /// <summary>
        /// Acquires a write lock (if not already taken)
        /// </summary>
        /// <returns>Disposable lock</returns>
        public IDisposable EnterWriteLock()
        {
            if (_modifyingLock.IsWriteLockHeld)
            {
                return new ActionDisposable(() => { });
            }
            IDisposable wl = _modifyingLock.WriteLock();
            return new ActionDisposable(() =>
                                            {
                                                RefreshSearchers();
                                                wl.Dispose();
                                            });
        }

        #endregion

        #region Index Merging

        /// <summary>
        /// Merges the Lucene directories into the base index
        /// </summary>
        /// <param name="directories">Directories to merge into this index</param>
        public void Merge(LDirectory[] directories)
        {
            if (directories == null)
                return;
            if (directories.Length == 0)
                return;

            using (EnterWriteLock())
            {
                using (var w = new Writer(_directory, _analyzer ?? new StandardAnalyzer()))
                {
                    w.AddIndexes(directories);
                }
            }
        }

        #endregion

        #region IndexSearchers Management

        private void ConstructSearchers()
        {
            using (EnterWriteLockWithoutSearcherRefresh())
            {
                _searcher = new IndexSearcher(_directory);
            }
        }

        private void RefreshSearchers()
        {
            // acquiring a writer lock will start queuing all search (reader) requests
            using (_modifyingLock.WriteLock())
            {
                CloseSearchers();
                ConstructSearchers();
            }
        }

        private void CloseSearchers()
        {
            using (EnterWriteLockWithoutSearcherRefresh())
            {
                if (_searcher != null)
                {
                    _searcher.Close();
                    _searcher = null;
                }
            }
        }

        #endregion

        #region IndexModifier Management

        private void ConstructModifier()
        {
            if (_directory == null) throw new InvalidOperationException("Directory is not specified");

            using (EnterWriteLockWithoutSearcherRefresh())
            {
                try
                {
                    _modifier = new IndexModifier(_directory, _analyzer, !DoesIndexExist());
                }
                catch (Exception)
                {
                    using (var writer = new IndexWriter(_directory, _analyzer, true))
                    {
                        writer.Close();
                    }
                    _modifier = new IndexModifier(_directory, _analyzer, false);
                }
            }
        }

        /// <summary>
        /// REVIEW: make private?
        /// </summary>
        public void RefreshModifier()
        {
            // acquiring a writer lock will start queuing all search (reader) requests
            using (EnterWriteLock())
            {
                CloseModifier();
                ConstructModifier();
            }
        }

        private void CloseModifier()
        {
            using (EnterWriteLockWithoutSearcherRefresh())
            {
                if (_modifier != null)
                {
                    _modifier.Close();
                    _modifier = null;
                }
            }
        }

        #endregion

        #region Utility

        private bool DoesIndexExist()
        {
            return IndexReader.IndexExists(_directory);
        }

        #endregion
    }
}